﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Cherry.Agile.Data.Metadata;
using System.Data.Objects.DataClasses;
using System.Data;
using System.Collections;
using Cherry.Agile.Data.Metadata.Interpreter;
using Cherry.Agile.Data.Object.Metadata;

namespace Cherry.Agile.Data.Metadata
{

    /// <summary>
    /// Parser of Binary expressions
    /// </summary>
    /// <typeparam name="T">type of entity</typeparam>
    internal sealed class BinaryExpressionParser<T> : IExpressionParser
        where T : class
    {

        #region Members

        string principalCondition = default(String);
        string principalRightCondition = default(string);
        string principalLeftCondition = default(string);

        #endregion

        #region Private Methods

        /// <summary>
        /// Parse operator name to SQL syntax
        /// </summary>
        /// <param name="nodeTypeName">Name of Node Type</param>
        /// <returns>Sql syntax operator</returns>
        private string GetOperatorString(ExpressionType nodeTypeName)
        {
            switch (nodeTypeName)
            {
                case ExpressionType.OrElse:
                    {
                        return InterpreterFactory.GetTokenFactory().OrElseOperator;
                    }
                case ExpressionType.AndAlso:
                    {
                        return InterpreterFactory.GetTokenFactory().AndAlsoOperator;
                    }
                case ExpressionType.Equal:
                    {
                        return InterpreterFactory.GetTokenFactory().EqualOperator;
                    }
                case ExpressionType.GreaterThan:
                    {
                        return InterpreterFactory.GetTokenFactory().GreaterThanOperator;
                    }
                case ExpressionType.GreaterThanOrEqual:
                    {
                        return InterpreterFactory.GetTokenFactory().GreaterThanOrEqualOperator;
                    }
                case ExpressionType.LessThan:
                    {
                        return InterpreterFactory.GetTokenFactory().LessThanOperator;
                    }
                case ExpressionType.LessThanOrEqual:
                    {
                        return InterpreterFactory.GetTokenFactory().LessThanOrEqualOperator;
                    }
                case ExpressionType.NotEqual:
                    {
                        return InterpreterFactory.GetTokenFactory().NotEqualOperator;
                    }

                default: throw new NotImplementedException("BinaryExpressionType not recognized");
            }
        }
        /// <summary>
        /// Validate if expression is simple comparer expression or not
        /// </summary>
        /// <param name="nodeType">Name of Node Type</param>
        /// <returns>Tru if is simple comparer expression ( =  ...)</returns>
        private bool IsSimpleExpression(ExpressionType nodeType)
        {
            return (nodeType == ExpressionType.AndAlso || nodeType == ExpressionType.OrElse)
                ? false
                : true;
        }
        /// <summary>
        /// Obtain string expression 
        /// </summary>
        /// <param name="expression">BinaryExpression to extract string sql syntax expression</param>
        /// <returns>Sql Syntax expression</returns>
        private string GetStringExpression(string containerName, Expression expression)
        {
            if (expression != null)
            {
                if (expression is BinaryExpression)
                {
                    BinaryExpression binaryExpression = expression as BinaryExpression;

                    if (IsSimpleExpression(expression.NodeType))
                    {
                        string leftEquation = string.Empty;
                        string rightEquation = string.Empty;

                        if (binaryExpression.Left is ConstantExpression)
                        {
                            leftEquation = InterpreterFactory.GetInterpreter(typeof(ConstantExpression)).InterpreteExpression<T>(containerName,binaryExpression.Left);
                        }
                        else if (binaryExpression.Left is MethodCallExpression)
                        {
                            leftEquation = InterpreterFactory.GetInterpreter(typeof(MethodCallExpression)).InterpreteExpression<T>(containerName,binaryExpression.Left);
                        }
                        else if (binaryExpression.Left is MemberExpression)
                        {
                            if (binaryExpression.Left.NodeType == ExpressionType.MemberAccess)
                                leftEquation = MetadataAccessor.GetColumnNameByEdmProperty<T>(containerName,binaryExpression.Left.ToString().Split('.')[1]);
                            else
                                throw new InvalidOperationException("Expression Tree is not supported");
                        }
                        else if (binaryExpression.Left is UnaryExpression)
                        {
                            var unary = binaryExpression.Left as UnaryExpression;
                            var exp = unary.Operand as MemberExpression;
                            leftEquation = MetadataAccessor.GetColumnNameByEdmProperty<T>(containerName, exp.Member.Name);
                        }
                        else
                            throw new InvalidOperationException("Expression Tree is not supported");


                        if (binaryExpression.Right is UnaryExpression)
                        {
                            UnaryExpression unaryExpression = binaryExpression.Right as UnaryExpression;
                            rightEquation = InterpreterFactory.GetInterpreter(unaryExpression.Operand.GetType()).InterpreteExpression<T>(containerName, unaryExpression.Operand);
                        }
                        else if (binaryExpression.Right is ConstantExpression)
                            rightEquation = InterpreterFactory.GetInterpreter(typeof(ConstantExpression)).InterpreteExpression<T>(containerName,binaryExpression.Right);
                        
                        else if (binaryExpression.Right is MemberExpression)
                        {
                            MemberExpression memberExpression = binaryExpression.Right as MemberExpression;
                            rightEquation = InterpreterFactory.GetInterpreter(typeof(MemberExpression)).InterpreteExpression<T>(containerName,memberExpression);
                        }
                        else if (binaryExpression.Right is MethodCallExpression)
                        {
                            MethodCallExpression methodCallExpression = binaryExpression.Right as MethodCallExpression;
                            rightEquation = InterpreterFactory.GetInterpreter(typeof(MethodCallExpression)).InterpreteExpression<T>(containerName,methodCallExpression);
                        }
                        else
                            rightEquation = MetadataAccessor.GetColumnNameByEdmProperty<T>(containerName,binaryExpression.Right.ToString().Split('.')[1]);


                        return string.Format("({0}){1}({2})", leftEquation, GetOperatorString(binaryExpression.NodeType), rightEquation);
                    }
                    else
                    {
                        return string.Format("({0}{1}{2})", GetStringExpression(containerName,binaryExpression.Left),
                            GetOperatorString(binaryExpression.NodeType),
                            GetStringExpression(containerName,binaryExpression.Right));
                    }
                }
                else if (expression is UnaryExpression)
                {
                    UnaryExpression unaryExpression = expression as UnaryExpression;
                    var exp = unaryExpression.Operand;

                    if (exp != null && unaryExpression.Operand.NodeType == ExpressionType.MemberAccess 
                            && (exp as MemberExpression).Expression.NodeType == ExpressionType.Constant)
                        return InterpreterFactory.GetInterpreter(exp.NodeType).InterpreteExpression<T>(containerName, exp);
                    else if (exp.NodeType == ExpressionType.MemberAccess)
                        return MetadataAccessor.GetColumnNameByEdmProperty<T>(containerName, (exp as MemberExpression).Member.Name);
                    else
                        return MetadataAccessor.GetColumnNameByEdmProperty<T>(containerName, expression.ToString());

                    //return InterpreterFactory.GetInterpreter(unaryExpression.Operand.GetType()).InterpreteExpression<T>(containerName,unaryExpression.Operand);
                }
                else if (expression is MemberExpression)
                {
                    var exp = expression as MemberExpression;
                    if (expression.NodeType == ExpressionType.MemberAccess && exp.Expression.NodeType == ExpressionType.Constant)
                        return InterpreterFactory.GetInterpreter(exp.NodeType).InterpreteExpression<T>(containerName, exp);
                    else if (expression.NodeType == ExpressionType.MemberAccess)
                        return MetadataAccessor.GetColumnNameByEdmProperty<T>(containerName, (expression as MemberExpression).Member.Name);
                    else
                        return MetadataAccessor.GetColumnNameByEdmProperty<T>(containerName, expression.ToString());
                }
                else if (expression is ConstantExpression)
                {
                    return InterpreterFactory.GetInterpreter(typeof(ConstantExpression)).InterpreteExpression<T>(containerName,expression);
                }
                else if (expression is MethodCallExpression)
                {
                    return InterpreterFactory.GetInterpreter(typeof(MethodCallExpression)).InterpreteExpression<T>(containerName,expression);
                }
                else
                    throw new InvalidExpressionException("Invalid Expression Three");
            }
            else
                throw new NullReferenceException("NULL Expression");
        }
        #endregion

        #region IExpressionParser Members

        Expression mExpression;
        /// <summary>
        /// <see cref="Cherry.Agile.Data.Metadata.IExpressionParser"/>
        /// </summary>
        public System.Linq.Expressions.Expression Expression
        {
            get
            {
                return mExpression;
            }
            set
            {
                mExpression = value;
            }
        }
        /// <summary>
        /// <see cref="Cherry.Agile.Data.Metadata.IExpressionParser"/>
        /// </summary>
        /// <returns> <see cref="Cherry.Agile.Data.Metadata.IExpressionParser"/></returns>
        public string ParseExpression(string containerName)
        {
            if (Expression == null)
                throw new InvalidOperationException("WhereExpression is empty");

            LambdaExpression expression = Expression as LambdaExpression;
            if (expression == null)
                throw new InvalidOperationException("WhereExpression not is LambdaExpression");

            Expression<Func<T, bool>> inner = expression as Expression<Func<T, bool>>;
            if (inner == null)
                throw new NotImplementedException("WhereExpression not is valid Expression");

            if (inner.Body.NodeType == ExpressionType.Call)
            {
                var callExp = inner.Body as MethodCallExpression;
                var left = callExp.Arguments[0] as MemberExpression;
               
                if (callExp.Arguments.Count == 1) //Like 
                {
                    principalCondition = " Like ";
                   
                    //var val = GetConstantExpressionValue(left);
                    var ri  = InterpreterFactory.GetInterpreter(callExp.NodeType).InterpreteExpression<T>(containerName, callExp);
                    return string.Format(" WHERE ({0})", ri);
                    //this.principalRightCondition = GetLikeSymbols(callExp.Method.Name,val==null?string.Empty:val.ToString());
                    //this.principalLeftCondition = GetStringExpression(containerName, callExp.Object as Expression);
                }
                else //in
                {
                    if (callExp.Method.Name != "Contains")
                        throw new NotSupportedException("暂不支持条件表达式中的存在该操作:" + callExp.Method.Name); 
                    var isValueObject = !left.Type.Name.StartsWith("String");
                    IEnumerable values = GetConstantExpressionValue(left) as IEnumerable; 
                    //(Expression.Lambda(left).Compile().DynamicInvoke() as IEnumerable;

                    var right = callExp.Arguments[1] as MemberExpression;

                    principalCondition = " In";
                    this.principalLeftCondition = GetStringExpression(containerName, right as Expression);
                    this.principalRightCondition = GetContainsValuesString(values, isValueObject);
                }

                
            }
            else
            {
                BinaryExpression binaryExpression = inner.Body as BinaryExpression;
                if (binaryExpression == null)
                    throw new NotImplementedException("Expression not valid to parse"); 

                this.principalCondition = GetOperatorString(binaryExpression.NodeType);

                this.principalLeftCondition = GetStringExpression(containerName,binaryExpression.Left as Expression);

                this.principalRightCondition = GetStringExpression(containerName,binaryExpression.Right as Expression);


            }
            return string.Format(" WHERE ({0}{1}{2})", principalLeftCondition, principalCondition, principalRightCondition);

        }

        #endregion
        internal static object GetConstantExpressionValue(Expression expression)
        {
            var me = expression as MemberExpression;

           
            object obj;
            if (me.Expression.NodeType == ExpressionType.Constant)
                obj = ((ConstantExpression)me.Expression).Value;
            else
                obj = GetConstantExpressionValue(me.Expression as MemberExpression);

            var field = me.Member as System.Reflection.FieldInfo;
            if (field != null)
            {
               var val = field.GetValue(obj);
                return val;
            }

            var pr = me.Member as System.Reflection.PropertyInfo;
            if (pr != null)
                return pr.GetValue(obj,null);

            return null;
        }
        private static ConstantExpression FindConstantExpression(MemberExpression exp)
        {
            if (exp.Expression.NodeType == ExpressionType.Constant)
                return exp.Expression as ConstantExpression;
            return FindConstantExpression(exp.Expression as MemberExpression);
        }

        private static string GetLikeSymbols(string methodName,string value)
        {
            if (methodName == "EndsWith")
                value = "%" + value;
            else if (methodName == "StartsWith")
                value = value + "%";
            else if (methodName == "Contains")
                value = "%" + value + "%";
            return "'" + value +"'";
           
        }
        internal static string GetContainsValuesString(IEnumerable values, bool isValueObject)
        {
            List<string> lst = new List<string>();
            string format = isValueObject ? "{0}" : "'{0}'";
            foreach (var item in values)
            {
                if (item != null)
                    lst.Add( string.Format(format,item) );
            }
            var ary = lst.ToArray();
            return  "(" +  string.Join(",", ary) + ")";
        }
    }
}
